package org.cocos2d.particlesystem;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;

import org.cocos2d.actions.UpdateCallback;
import org.cocos2d.config.ccConfig;
import org.cocos2d.config.ccMacros;
import org.cocos2d.nodes.CCNode;
import org.cocos2d.nodes.CCTextureCache;
import org.cocos2d.opengl.CCTexture2D;
import org.cocos2d.protocols.CCTextureProtocol;
import org.cocos2d.types.CGPoint;
import org.cocos2d.types.ccBlendFunc;
import org.cocos2d.types.ccColor4F;
import org.cocos2d.types.ccPointSprite;
import org.cocos2d.types.util.CGPointUtil;
import org.cocos2d.types.util.PoolHolder;
import org.cocos2d.types.util.ccColor4FUtil;
import org.cocos2d.utils.Base64;
import org.cocos2d.utils.pool.OneClassPool;

import java.io.IOException;
import java.util.HashMap;
import java.util.zip.DataFormatException;
import java.util.zip.Inflater;

import javax.microedition.khronos.opengles.GL10;

// typedef void (*CC_UPDATE_PARTICLE_IMP)(id, SEL, tCCParticle*, CGPoint);

/**
 * Particle System base class
 * Attributes of a Particle System:
 * - emmision rate of the particles
 * - Gravity Mode (Mode A):
 * - gravity
 * - direction
 * - speed +-  variance
 * - tangential acceleration +- variance
 * - radial acceleration +- variance
 * - Radius Mode (Mode B):
 * - startRadius +- variance
 * - endRadius +- variance
 * - rotate +- variance
 * - Properties common to all modes:
 * - life +- life variance
 * - start spin +- variance
 * - end spin +- variance
 * - start size +- variance
 * - end size +- variance
 * - start color +- variance
 * - end color +- variance
 * - life +- variance
 * - blending function
 * - texture
 * <p>
 * cocos2d also supports particles generated by Particle Designer (http://particledesigner.71squared.com/).
 * 'Radius Mode' in Particle Designer uses a fixed emit rate of 30 hz. Since that can't be guarateed in cocos2d,
 * cocos2d uses a another approach, but the results are almost identical.
 * <p>
 * cocos2d supports all the variables used by Particle Designer plus a bit more:
 * - spinning particles (supported when using CCQuadParticleSystem)
 * - tangential acceleration (Gravity mode)
 * - radial acceleration (Gravity mode)
 * - radius direction (Radius mode) (Particle Designer supports outwards to inwards direction only)
 * <p>
 * It is possible to customize any of the above mentioned properties in runtime. Example:
 *
 * @code emitter.radialAccel = 15;
 * emitter.startSpin = 0;
 * @endcode
 */
public abstract class CCParticleSystem extends CCNode implements CCTextureProtocol, UpdateCallback {

    /**
     * The Particle emitter lives forever
     */
    public static final int kCCParticleDurationInfinity = -1;

    /**
     * The starting size of the particle is equal to the ending size
     */
    public static final int kCCParticleStartSizeEqualToEndSize = -1;

    /**
     * The starting radius of the particle is equal to the ending radius
     */
    public static final int kCCParticleStartRadiusEqualToEndRadius = -1;

    // backward compatible
    public static final int kParticleStartSizeEqualToEndSize = kCCParticleStartSizeEqualToEndSize;
    public static final int kParticleDurationInfinity = kCCParticleDurationInfinity;

    /**
     * Gravity mode (A mode)
     */
    public static final int kCCParticleModeGravity = 0;

    /**
     * Radius mode (B mode)
     */
    public static final int kCCParticleModeRadius = 1;


    /** @typedef tCCPositionType
    possible types of particle positions
     */
    /**
     * If the emitter is repositioned, the living particles won't be repositioned
     */
    public static final int kCCPositionTypeFree = 0;

    /**
     * Living particles are attached to the world but will follow the emitter repositioning.
     * Use case: Attach an emitter to an sprite, and you want that the emitter follows the sprite.
     */
    public static final int kCCPositionTypeRelative = 1;

    /**
     * If the emitter is repositioned, the living particles will be repositioned too
     */
    public static final int kCCPositionTypeGrouped = 2;
    protected int id;
    // is the particle system active ?
    protected boolean active;

    // duration in seconds of the system. -1 is infinity
    protected float duration;

    // time elapsed since the start of the system (in seconds)
    protected float elapsed;

    // Optimization
    //Method	updateParticleImp;
    // String	updateParticleSel;
    /// Gravity of the particles
    protected CGPoint centerOfGravity = CGPoint.zero();
    // position is from "superclass" CocosNode
    // Emitter source position
    protected CGPoint source = CGPoint.zero();
    // Position variance
    protected CGPoint posVar = CGPoint.zero();
    // The angle (direction) of the particles measured in degrees
    protected float angle;
    // Angle variance measured in degrees;
    protected float angleVar;
    // Size of the particles
    protected float size;
    // Size variance
    protected float sizeVar;
    // How many seconds will the particle live
    protected float life;
    // Life variance
    protected float lifeVar;
    // Start color of the particles
    protected ccColor4F startColor = new ccColor4F();
    // Start color variance
    protected ccColor4F startColorVar = new ccColor4F();
    // End color of the particles
    protected ccColor4F endColor = new ccColor4F();
    // End color variance
    protected ccColor4F endColorVar = new ccColor4F();
    // Whether or not the node will be auto-removed when there are not particles
    protected boolean autoRemoveOnFinish_;
    // Array of particles
    protected CCParticle[] particles;
    // Maximum particles
    protected int totalParticles;
    // Count of active particles
    protected int particleCount;
    // color modulate
    protected boolean colorModulate;
    // How many particles can be emitted per second
    protected float emissionRate;
    protected float emitCounter;
    // Texture of the particles
    protected CCTexture2D texture;
    // vertices buffer id
    protected int verticesID = -1;
    // colors buffer id
    protected int colorsID;
    //  particle idx
    protected int particleIdx;
    // start ize of the particles
    float startSize;
    // start Size variance
    float startSizeVar;
    // End size of the particle
    float endSize;
    // end size of variance
    float endSizeVar;
    // start angle of the particles
    float startSpin;

//	// The speed the particles will have.
//	protected float speed;
//	// The speed variance
//	protected float speedVar;
//
//	// Tangential acceleration
//	protected float tangentialAccel;
//
//	// Tangential acceleration variance
//	protected float tangentialAccelVar;
//
//	// Radial acceleration
//	protected float radialAccel;
//
//	// Radial acceleration variance
//	protected float radialAccelVar;
    // start angle variance
    float startSpinVar;
    // End angle of the particle
    float endSpin;
    // end angle ariance
    float endSpinVar;
    // blend function
    ccBlendFunc blendFunc = new ccBlendFunc(ccConfig.CC_BLEND_SRC, ccConfig.CC_BLEND_DST);
    // Different modes
    int emitterMode = -1;
    ModeA modeA;
    ModeB modeB;
    // Array of (x,y,size,color)
    ccPointSprite[] vertices;
    // movement type: free or grouped
    private int positionType_;

    //! Initializes a system with a fixed number of particles
    protected CCParticleSystem(int numberOfParticles) {
        initWithNumberOfParticles(numberOfParticles);
    }

    protected CCParticleSystem() {

    }

    /**
     * creates an initializes a CCParticleSystem from a plist file.
     * This plist files can be creted manually or with Particle Designer:
     * http://particledesigner.71squared.com/
     *
     * @since v0.99.3
     */
    public static CCParticleSystem particleWithFile(String plistFile) {
        //    return new CCParticleSystem(plistFile);
        return null;
    }

    public void setStartSize(float s) {
        startSize = s;
    }

    public void setStartSizeVar(float ssv) {
        startSizeVar = ssv;
    }

    public void setEndSize(float s) {
        endSize = s;
    }

    public void setEndSizeVar(float esv) {
        endSizeVar = esv;
    }

    public void setStartSpin(float s) {
        startSpin = s;
    }

    public void setStartSpinVar(float ssv) {
        startSpinVar = ssv;
    }

    public void setEndSpin(float es) {
        endSpin = es;
    }

    public void setEndSpinVar(float esv) {
        endSpinVar = esv;
    }

    // movment type: free or grouped
//	protected int	positionType;

    public CGPoint getCenterOfGravity() {
        return CGPoint.ccp(centerOfGravity.x, centerOfGravity.y);
    }

    public void setCenterOfGravity(CGPoint p) {
        centerOfGravity = CGPoint.make(p.x, p.y);
    }

    public void setAngle(float a) {
        angle = a;
    }

    public void setAngleVar(float av) {
        angleVar = av;
    }

    public void setLifeVar(float lv) {
        lifeVar = lv;
    }

//	// additive color or blend
//	protected boolean blendAdditive;

    public ccColor4F getStartColor() {
        return new ccColor4F(startColor);
    }

    public void setStartColor(ccColor4F sc) {
        ccColor4FUtil.copy(sc, startColor);
    }

    public ccColor4F getStartColorRef() {
        return startColor;
    }

    public ccColor4F getStartColorVar() {
        return new ccColor4F(startColorVar);
    }

    public void setStartColorVar(ccColor4F scv) {
        ccColor4FUtil.copy(scv, startColorVar);
    }

    public ccColor4F getStartColorVarRef() {
        return startColorVar;
    }

    public void setEndColor(ccColor4F ec) {
        ccColor4FUtil.copy(ec, endColor);
    }

    public ccColor4F getEndColorRef() {
        return endColor;
    }

    public void setEndColorVar(ccColor4F ecv) {
        ccColor4FUtil.copy(ecv, endColorVar);
    }

    public ccColor4F getEndColorVarRef() {
        return endColorVar;
    }

    // Array of colors
    //CCColorF	colors[];

    // Array of pointsizes
    //float pointsizes[];

    public int getTotalParticles() {
        return totalParticles;
    }

    public void setEmissionRate(float er) {
        emissionRate = er;
    }

    public void setEmitterMode(int em) {
        if (emitterMode == em)
            return;
        emitterMode = em;
        if (em == kCCParticleModeGravity) {
            modeA = new ModeA();
            if (modeB != null)
                modeB = null;
        } else {
            modeB = new ModeB();
            if (modeA != null)
                modeA = null;
        }
    }

    public void setAutoRemoveOnFinish(boolean ar) {
        autoRemoveOnFinish_ = ar;
    }

    //! whether or not the system is full
    public boolean isFull() {
        return (particleCount == totalParticles);
    }

    public float getTangentialAccel() {
        assert emitterMode == kCCParticleModeGravity : "Particle Mode should be Gravity";
        return modeA.tangentialAccel;
    }

    public void setTangentialAccel(float t) {
        assert (emitterMode == kCCParticleModeGravity) : "Particle Mode should be Gravity";
        modeA.tangentialAccel = t;
    }

    public float getTangentialAccelVar() {
        assert emitterMode == kCCParticleModeGravity : "Particle Mode should be Gravity";
        return modeA.tangentialAccelVar;
    }

    public void setTangentialAccelVar(float t) {
        assert emitterMode == kCCParticleModeGravity : "Particle Mode should be Gravity";
        modeA.tangentialAccelVar = t;
    }

    public float getRadialAccel() {
        assert emitterMode == kCCParticleModeGravity : "Particle Mode should be Gravity";
        return modeA.radialAccel;
    }

    public void setRadialAccel(float t) {
        assert emitterMode == kCCParticleModeGravity : "Particle Mode should be Gravity";
        modeA.radialAccel = t;
    }

    public float getRadialAccelVar() {
        assert emitterMode == kCCParticleModeGravity : "Particle Mode should be Gravity";
        return modeA.radialAccelVar;
    }

    public void setRadialAccelVar(float t) {
        assert emitterMode == kCCParticleModeGravity : "Particle Mode should be Gravity";
        modeA.radialAccelVar = t;
    }

    /**
     * Gravity value
     */
    public CGPoint getGravity() {
        return modeA.gravity;
    }

    public void setGravity(CGPoint g) {
        assert emitterMode == kCCParticleModeGravity : "Particle Mode should be Gravity";
        modeA.gravity.set(g);
    }

    public CGPoint gravity() {
        assert emitterMode == kCCParticleModeGravity : "Particle Mode should be Gravity";
        return modeA.gravity;
    }

    public float getSpeed() {
        assert emitterMode == kCCParticleModeGravity : "Particle Mode should be Gravity";
        return modeA.speed;
    }

    public void setSpeed(float speed) {
        assert emitterMode == kCCParticleModeGravity : "Particle Mode should be Gravity";
        modeA.speed = speed;
    }

    public float getSpeedVar() {
        assert emitterMode == kCCParticleModeGravity : "Particle Mode should be Gravity";
        return modeA.speedVar;
    }

    public void setSpeedVar(float speedVar) {
        assert emitterMode == kCCParticleModeGravity : "Particle Mode should be Gravity";
        modeA.speedVar = speedVar;
    }

    public void setStartRadius(float startRadius) {
        assert emitterMode == kCCParticleModeRadius : "Particle Mode should be Radius";
        modeB.startRadius = startRadius;
    }

    public float startRadius() {
        assert emitterMode == kCCParticleModeRadius : "Particle Mode should be Radius";
        return modeB.startRadius;
    }

    public void setStartRadiusVar(float startRadiusVar) {
        assert emitterMode == kCCParticleModeRadius : "Particle Mode should be Radius";
        modeB.startRadiusVar = startRadiusVar;
    }

    public float startRadiusVar() {
        assert emitterMode == kCCParticleModeRadius : "Particle Mode should be Radius";
        return modeB.startRadiusVar;
    }

    public void setEndRadius(float endRadius) {
        assert emitterMode == kCCParticleModeRadius : "Particle Mode should be Radius";
        modeB.endRadius = endRadius;
    }

    public float endRadius() {
        assert emitterMode == kCCParticleModeRadius : "Particle Mode should be Radius";
        return modeB.endRadius;
    }

    public void setEndRadiusVar(float endRadiusVar) {
        assert emitterMode == kCCParticleModeRadius : "Particle Mode should be Radius";
        modeB.endRadiusVar = endRadiusVar;
    }

    public float endRadiusVar() {
        assert emitterMode == kCCParticleModeRadius : "Particle Mode should be Radius";
        return modeB.endRadiusVar;
    }

    /**
     * Is the emitter active
     */
    public boolean getActive() {
        return active;
    }

    /**
     * Quantity of particles that are being simulated at the moment
     */
    public int getParticleCount() {
        return particleCount;
    }

    public void setRotatePerSecond(float degrees) {
        // NSAssert( emitterMode_ == kCCParticleModeRadius, @"Particle Mode should be Radius");
        modeB.rotatePerSecond = degrees;
    }

    public float rotatePerSecond() {
        // NSAssert( emitterMode_ == kCCParticleModeRadius, @"Particle Mode should be Radius");
        return modeB.rotatePerSecond;
    }

    public void setRotatePerSecondVar(float degrees) {
        // NSAssert( emitterMode_ == kCCParticleModeRadius, @"Particle Mode should be Radius");
        modeB.rotatePerSecondVar = degrees;
    }

    public float rotatePerSecondVar() {
        // NSAssert( emitterMode_ == kCCParticleModeRadius, @"Particle Mode should be Radius");
        return modeB.rotatePerSecondVar;
    }


    /**
     * How many seconds the emitter wil run. -1 means 'forever'
     */

    public float getDuration() {
        return duration;
    }

    public void setDuration(float duration) {
        this.duration = duration;
    }

    /**
     * Source location of particles respective to emitter location
     */
    public CGPoint getSource() {
        return source;
    }

    public void setSource(CGPoint source) {
        this.source = source;
    }

    /**
     * Position variance of the emitter
     */
    public CGPoint getPosVar() {
        return posVar;
    }

    public void setPosVar(CGPoint pv) {
        posVar = CGPoint.make(pv.x, pv.y);
    }

    /**
     * life, and life variation of each particle
     */
    public float getLife() {
        return life;
    }

    //    /** life variance of each particle */
    //    protected float lifeVar;
    //    /** angle and angle variation of each particle */
    //    protected float angle;
    //    /** angle variance of each particle */
    //    protected float angleVar;
    //    /** speed of each particle */
    //    protected float speed;
    //    /** speed variance of each particle */
    //    protected float speedVar;
    //    /** tangential acceleration of each particle */
    //    protected float tangentialAccel;
    //    /** tangential acceleration variance of each particle */
    //    protected float tangentialAccelVar;
    //    /** radial acceleration of each particle */
    //    protected float radialAccel;
    //    /** radial acceleration variance of each particle */
    //    protected float radialAccelVar;
    //    /** size in pixels of each particle */
    //    protected float size;
    //    /** size variance in pixels of each particle */
    //    protected float sizeVar;
    //    /** start color of each particle */
    //    protected CCColorF startColor;
    //    /** start color variance of each particle */
    //    protected CCColorF startColorVar;
    //    /** end color and end color variation of each particle */
    //    protected CCColorF endColor;
    //    /** end color variance of each particle */
    //    protected CCColorF endColorVar;
    //    /** emission rate of the particles */
    //    protected float emissionRate;
    //    /** maximum particles of the system */
    //    protected int totalParticles;

//	public static final int kPositionTypeFree = 1;
//	public static final int kPositionTypeGrouped = 2;

    public void setLife(float life) {
        this.life = life;
    }

    public int getPositionType() {
        return positionType_;
    }

    public void setPositionType(int type) {
        positionType_ = type;
    }


    /**
     * texture used to render the particles
     */

    public CCTexture2D getTexture() {
        return texture;
    }

    public void setTexture(CCTexture2D tex) {
        texture = tex;

        // If the new texture has No premultiplied alpha, AND the blendFunc hasn't been changed, then update it
        if (texture != null && !texture.hasPremultipliedAlpha() &&
                (blendFunc.src == ccConfig.CC_BLEND_SRC && blendFunc.dst == ccConfig.CC_BLEND_DST)) {

            blendFunc.src = GL10.GL_SRC_ALPHA;
            blendFunc.dst = GL10.GL_ONE_MINUS_SRC_ALPHA;
        }
    }

    protected void initWithNumberOfParticles(int numberOfParticles) {
        totalParticles = numberOfParticles;

        particles = new CCParticle[totalParticles];

        for (int i = 0; i < totalParticles; i++) {
            particles[i] = new CCParticle();
        }

        // default, active
        active = true;

        // default movement type;
        positionType_ = kCCPositionTypeFree;

        // by default be in mode A:
        this.setEmitterMode(kCCParticleModeGravity);

        // default: modulate
        // XXX: not used
        //	colorModulate = YES;

        autoRemoveOnFinish_ = false;

        // profiling
        // Optimization: compile udpateParticle method
		/* updateParticleSel = "updateQuad";

		// updateParticleImp = null;
		try {
			updateParticleImp = this.getClass().getMethod(updateParticleSel, new Class[]{CCParticle.class, CGPoint.class});
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}*/

        // udpate after action in run!
        this.scheduleUpdate(1);
    }

    private void initParticle(CCParticle particle) {
        // timeToLive
        // no negative life. prevent division by 0
        particle.timeToLive = Math.max(0, life + lifeVar * ccMacros.CCRANDOM_MINUS1_1());

        // position
        particle.pos.set(centerOfGravity.x + posVar.x * ccMacros.CCRANDOM_MINUS1_1(),
                centerOfGravity.y + posVar.y * ccMacros.CCRANDOM_MINUS1_1());

        // Color
//        ccColor4F start = new ccColor4F();
        float start_r = Math.min(1, Math.max(0, startColor.r + startColorVar.r * ccMacros.CCRANDOM_MINUS1_1()));
        float start_g = Math.min(1, Math.max(0, startColor.g + startColorVar.g * ccMacros.CCRANDOM_MINUS1_1()));
        float start_b = Math.min(1, Math.max(0, startColor.b + startColorVar.b * ccMacros.CCRANDOM_MINUS1_1()));
        float start_a = Math.min(1, Math.max(0, startColor.a + startColorVar.a * ccMacros.CCRANDOM_MINUS1_1()));

//        ccColor4F end = new ccColor4F();
        float end_r = Math.min(1, Math.max(0, endColor.r + endColorVar.r * ccMacros.CCRANDOM_MINUS1_1()));
        float end_g = Math.min(1, Math.max(0, endColor.g + endColorVar.g * ccMacros.CCRANDOM_MINUS1_1()));
        float end_b = Math.min(1, Math.max(0, endColor.b + endColorVar.b * ccMacros.CCRANDOM_MINUS1_1()));
        float end_a = Math.min(1, Math.max(0, endColor.a + endColorVar.a * ccMacros.CCRANDOM_MINUS1_1()));

        ccColor4FUtil.set(particle.color, start_r, start_g, start_b, start_a);

        ccColor4FUtil.set(particle.deltaColor,
                (end_r - start_r) / particle.timeToLive,
                (end_g - start_g) / particle.timeToLive,
                (end_b - start_b) / particle.timeToLive,
                (end_a - start_a) / particle.timeToLive);

        // size
        float startS = Math.max(0, startSize + startSizeVar * ccMacros.CCRANDOM_MINUS1_1()); // no negative size

        particle.size = startS;
        if (endSize == kCCParticleStartSizeEqualToEndSize)
            particle.deltaSize = 0;
        else {
            float endS = endSize + endSizeVar * ccMacros.CCRANDOM_MINUS1_1();
            endS = Math.max(0, endS);
            particle.deltaSize = (endS - startS) / particle.timeToLive;
        }

        // rotation
        float startA = startSpin + startSpinVar * ccMacros.CCRANDOM_MINUS1_1();
        float endA = endSpin + endSpinVar * ccMacros.CCRANDOM_MINUS1_1();
        particle.rotation = startA;
        particle.deltaRotation = (endA - startA) / particle.timeToLive;

        // position
        if (positionType_ == kCCPositionTypeFree) {
            this.convertToWorldSpace(0, 0, particle.startPos);
        } else if (positionType_ == kCCPositionTypeRelative) {
            particle.startPos.set(position_);
        }

        // direction
        float a = ccMacros.CC_DEGREES_TO_RADIANS(angle + angleVar * ccMacros.CCRANDOM_MINUS1_1());

        // Mode Gravity: A
        if (emitterMode == kCCParticleModeGravity) {
            float s = modeA.speed + modeA.speedVar * ccMacros.CCRANDOM_MINUS1_1();

            if (particle.modeA == null) {
                particle.modeA = new CCParticle.ParticleModeA();
            }

            // direction
            particle.modeA.dir.set((float) Math.cos(a), (float) Math.sin(a));
            CGPointUtil.mult(particle.modeA.dir, s);

            // radial accel
            particle.modeA.radialAccel = modeA.radialAccel + modeA.radialAccelVar * ccMacros.CCRANDOM_MINUS1_1();

            // tangential accel
            particle.modeA.tangentialAccel = modeA.tangentialAccel + modeA.tangentialAccelVar * ccMacros.CCRANDOM_MINUS1_1();
        }

        // Mode Radius: B
        else {
            // Set the default diameter of the particle from the source position
            float startRadius = modeB.startRadius + modeB.startRadiusVar * ccMacros.CCRANDOM_MINUS1_1();
            float endRadius = modeB.endRadius + modeB.endRadiusVar * ccMacros.CCRANDOM_MINUS1_1();

            if (particle.modeB == null) {
                particle.modeB = new CCParticle.ParticleModeB();
            }

            particle.modeB.radius = startRadius;

            if (modeB.endRadius == kCCParticleStartRadiusEqualToEndRadius)
                particle.modeB.deltaRadius = 0;
            else
                particle.modeB.deltaRadius = (endRadius - startRadius) / particle.timeToLive;

            particle.modeB.angle = a;
            particle.modeB.degreesPerSecond = ccMacros.CC_DEGREES_TO_RADIANS(modeB.rotatePerSecond + modeB.rotatePerSecondVar * ccMacros.CCRANDOM_MINUS1_1());
        }
    }

    //! stop emitting particles. Running particles will continue to run until they die
    public void stopSystem() {
        active = false;
        elapsed = duration;
        emitCounter = 0;
    }

    //! Kill all living particles.
    public void resetSystem() {
        active = true;
        elapsed = 0;
        for (particleIdx = 0; particleIdx < particleCount; ++particleIdx) {
            CCParticle p = particles[particleIdx];
            p.timeToLive = 0;
        }
    }

    protected void loadParticleFile(HashMap<String, Object> dictionary) {
        assert (dictionary != null) : "A dictionary object is expected.";

        // angle
        setAngle(((Number) dictionary.get("angle")).floatValue());
        setAngleVar(((Number) dictionary.get("angleVariance")).floatValue());

        // duration
        setDuration(((Number) dictionary.get("duration")).floatValue());

        // blend function
        setBlendFunc(new ccBlendFunc(((Number) dictionary.get("blendFuncSource")).intValue(),
                ((Number) dictionary.get("blendFuncDestination")).intValue()));

        // color
        float r, g, b, a;

        r = ((Number) dictionary.get("startColorRed")).floatValue();
        g = ((Number) dictionary.get("startColorGreen")).floatValue();
        b = ((Number) dictionary.get("startColorBlue")).floatValue();
        a = ((Number) dictionary.get("startColorAlpha")).floatValue();
        setStartColor(new ccColor4F(r, g, b, a));

        r = ((Number) dictionary.get("startColorVarianceRed")).floatValue();
        g = ((Number) dictionary.get("startColorVarianceGreen")).floatValue();
        b = ((Number) dictionary.get("startColorVarianceBlue")).floatValue();
        a = ((Number) dictionary.get("startColorVarianceAlpha")).floatValue();
        setStartColorVar(new ccColor4F(r, g, b, a));

        r = ((Number) dictionary.get("finishColorRed")).floatValue();
        g = ((Number) dictionary.get("finishColorGreen")).floatValue();
        b = ((Number) dictionary.get("finishColorBlue")).floatValue();
        a = ((Number) dictionary.get("finishColorAlpha")).floatValue();
        setEndColor(new ccColor4F(r, g, b, a));

        r = ((Number) dictionary.get("finishColorVarianceRed")).floatValue();
        g = ((Number) dictionary.get("finishColorVarianceGreen")).floatValue();
        b = ((Number) dictionary.get("finishColorVarianceBlue")).floatValue();
        a = ((Number) dictionary.get("finishColorVarianceAlpha")).floatValue();
        setEndColorVar(new ccColor4F(r, g, b, a));

        // particle size
        setStartSize(((Number) dictionary.get("startParticleSize")).floatValue());
        setStartSizeVar(((Number) dictionary.get("startParticleSizeVariance")).floatValue());
        setEndSize(((Number) dictionary.get("finishParticleSize")).floatValue());
        setEndSizeVar(((Number) dictionary.get("finishParticleSizeVariance")).floatValue());

        // position
        float x = ((Number) dictionary.get("sourcePositionx")).floatValue();
        float y = ((Number) dictionary.get("sourcePositiony")).floatValue();
        setPosition(CGPoint.ccp(x, y));
        setPosVar(CGPoint.ccp(((Number) dictionary.get("sourcePositionVariancex")).floatValue(),
                ((Number) dictionary.get("sourcePositionVariancey")).floatValue()));

        setEmitterMode(((Number) dictionary.get("emitterType")).intValue());

        if (emitterMode == kCCParticleModeGravity) {
            // Mode A: Gravity + tangential accel + radial accel
            // gravity
            setGravity(CGPoint.ccp(((Number) dictionary.get("gravityx")).floatValue(),
                    ((Number) dictionary.get("gravityy")).floatValue()));

            //
            // speed
            setSpeed(((Number) dictionary.get("speed")).floatValue());
            setSpeedVar(((Number) dictionary.get("speedVariance")).floatValue());

            // radial acceleration
            setRadialAccel(((Number) dictionary.get("radialAcceleration")).floatValue());
            setRadialAccelVar(((Number) dictionary.get("radialAccelVariance")).floatValue());

            // tangential acceleration
            setTangentialAccel(((Number) dictionary.get("tangentialAcceleration")).floatValue());
            setTangentialAccelVar(((Number) dictionary.get("tangentialAccelVariance")).floatValue());
        } else {
            float maxRadius = ((Number) dictionary.get("maxRadius")).floatValue();
            float maxRadiusVar = ((Number) dictionary.get("maxRadiusVariance")).floatValue();
            float minRadius = ((Number) dictionary.get("minRadius")).floatValue();

            setStartRadius(maxRadius);
            setStartRadiusVar(maxRadiusVar);
            setEndRadius(minRadius);
            setEndRadiusVar(0);
            setRotatePerSecond(((Number) dictionary.get("rotatePerSecond")).floatValue());
            setRotatePerSecondVar(((Number) dictionary.get("rotatePerSecondVariance")).floatValue());
        }

        // life span
        setLife(((Number) dictionary.get("particleLifespan")).floatValue());
        setLifeVar(((Number) dictionary.get("particleLifespanVariance")).floatValue());

        // emission Rate
        setEmissionRate(getTotalParticles() / getLife());

        // texture
        // Try to get the texture from the cache
        String textureName = (String) dictionary.get("textureFileName");
        String textureData = (String) dictionary.get("textureImageData");

        boolean loaded = false;
        try {
            CCTexture2D tex = CCTextureCache.sharedTextureCache().addImage(textureName);
            setTexture(tex);
            loaded = true;
        } catch (Exception e) {
            e.printStackTrace();
        }

        // !!! bad for memory: Bitmap instance is staying in memory while system exists !!!
        if (!loaded && textureData != null) {
            // if it fails, try to get it from the base64-gzipped data
            byte[] buffer = null;

            try {
                buffer = Base64.decode(textureData);
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

            byte[] deflated = new byte[buffer.length];
            Inflater decompresser = new Inflater(false);

            int deflatedLen = 0;

            try {
                deflatedLen = decompresser.inflate(deflated);
            } catch (DataFormatException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

            Bitmap bmp = BitmapFactory.decodeByteArray(deflated, 0, deflatedLen);

            if (bmp != null) {
                setTexture(CCTextureCache.sharedTextureCache().addImage(bmp, textureName));
            }
        }
    }

    //! Add a particle to the emitter
    public boolean addParticle() {
        if (isFull())
            return false;
        CCParticle particle = particles[particleCount];
        initParticle(particle);
        particleCount++;
        return true;
    }

    // ideas taken from:
    //	 . The ocean spray in your face [Jeff Lander]
    //		http://www.double.co.nz/dust/col0798.pdf
    //	 . Building an Advanced Particle System [John van der Burg]
    //		http://www.gamasutra.com/features/20000623/vanderburg_01.htm
    //   . LOVE game engine
    //		http://love2d.org/
    //
    // Radius mode support, from 71 squared
    //		http://particledesigner.71squared.com/
    //
    // IMPORTANT: Particle Designer is supported by cocos2d, but
    // 'Radius Mode' in Particle Designer uses a fixed emit rate of 30 hz. Since that can't be guarateed in cocos2d,
    //  cocos2d uses a another approach, but the results are almost identical. 
    //

    public void update(float dt) {
        if (active && emissionRate != 0) {
            float rate = 1.0f / emissionRate;
            emitCounter += dt;
            while (particleCount < totalParticles && emitCounter > rate) {
                addParticle();
                emitCounter -= rate;
            }

            elapsed += dt;
            if (duration != -1 && duration < elapsed)
                stopSystem();
        }

        particleIdx = 0;

        OneClassPool<CGPoint> pointPool = PoolHolder.getInstance().getCGPointPool();
        CGPoint currentPosition = pointPool.get();
        CGPoint tmp = pointPool.get();
        CGPoint radial = pointPool.get();
        CGPoint tangential = pointPool.get();

        if (positionType_ == kCCPositionTypeFree) {
            convertToWorldSpace(0, 0, currentPosition);
        } else if (positionType_ == kCCPositionTypeRelative) {
            currentPosition.set(position_);
        }

        while (particleIdx < particleCount) {
            CCParticle p = particles[particleIdx];
            // life
            p.timeToLive -= dt;
            if (p.timeToLive > 0) {
                // Mode A: gravity, direction, tangential accel & radial accel
                if (emitterMode == kCCParticleModeGravity) {
//                    CGPoint tmp, radial, tangential;

                    CGPointUtil.zero(radial);
                    // radial acceleration
                    if (p.pos.x != 0 || p.pos.y != 0)
                        CGPointUtil.normalize(p.pos, radial);
                    tangential.set(radial);
                    CGPointUtil.mult(radial, p.modeA.radialAccel);

                    // tangential acceleration
                    float newy = tangential.x;
                    tangential.x = -tangential.y;
                    tangential.y = newy;
                    CGPointUtil.mult(tangential, p.modeA.tangentialAccel);

                    // (gravity + radial + tangential) * dt
                    CGPointUtil.add(radial, tangential, tmp);
                    CGPointUtil.add(tmp, modeA.gravity);
                    CGPointUtil.mult(tmp, dt);
                    CGPointUtil.add(p.modeA.dir, tmp);
                    CGPointUtil.mult(p.modeA.dir, dt, tmp);
                    CGPointUtil.add(p.pos, tmp);
                }
                // Mode B: radius movement
                else {
                    // Update the angle and radius of the particle.
                    p.modeB.angle += p.modeB.degreesPerSecond * dt;
                    p.modeB.radius += p.modeB.deltaRadius * dt;

                    p.pos.x = -(float) Math.cos(p.modeB.angle) * p.modeB.radius;
                    p.pos.y = -(float) Math.sin(p.modeB.angle) * p.modeB.radius;
                }

                // color
                p.color.r += (p.deltaColor.r * dt);
                p.color.g += (p.deltaColor.g * dt);
                p.color.b += (p.deltaColor.b * dt);
                p.color.a += (p.deltaColor.a * dt);

                // size
                p.size += (p.deltaSize * dt);
                p.size = Math.max(0, p.size);

                // angle
                p.rotation += (p.deltaRotation * dt);
                CGPoint newPos;

                if (positionType_ == kCCPositionTypeFree || positionType_ == kCCPositionTypeRelative) {
                    CGPoint diff = tmp;
                    CGPointUtil.sub(currentPosition, p.startPos, diff);
                    CGPointUtil.sub(p.pos, diff, diff);
                    newPos = diff;
                } else {
                    newPos = p.pos;
                }

                this.updateQuad(p, newPos);
                /* try {
					updateParticleImp.invoke(this, new Object[]{p, newPos});
				} catch (IllegalArgumentException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (InvocationTargetException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}*/

                // update particle counter
                particleIdx++;

            } else {
                // life < 0
                if (particleIdx != particleCount - 1) {
                    CCParticle tmpPart = particles[particleIdx];
                    particles[particleIdx] = particles[particleCount - 1];
                    particles[particleCount - 1] = tmpPart;
                }
                particleCount--;

                if (particleCount == 0 && autoRemoveOnFinish_) {
                    unscheduleUpdate();
                    this.getParent().removeChild(this, true);
                    return;
                }
            }
        }

        pointPool.free(currentPosition);
        pointPool.free(tmp);
        pointPool.free(radial);
        pointPool.free(tangential);

        postStep();
    }


//    /** initializes a CCParticleSystem from a plist file.
//      This plist files can be creted manually or with Particle Designer:
//        http://particledesigner.71squared.com/
//      @since v0.99.3
//    */
//    protected CCParticleSystem(String plistFile) {
//    	HashMap<String,Object> dictionary = PlistParser.parse(plistFile);
//    	int numParticles = ((Number)dictionary.get("maxParticles")).intValue();
//    	initWithNumberOfParticles(numParticles);
//    	loadParticleFile(dictionary);
//    }

    //! should be overriden by subclasses
    public void updateQuad(CCParticle particle, CGPoint pos) {
    }

    public void postStep() {
        // should be overriden
    }

    public boolean getBlendAdditive() {
        return (blendFunc.src == GL10.GL_SRC_ALPHA && blendFunc.dst == GL10.GL_ONE);
    }

    public void setBlendAdditive(boolean additive) {
        if (additive) {
            blendFunc.src = GL10.GL_SRC_ALPHA;
            blendFunc.dst = GL10.GL_ONE;
        } else {
            if (texture != null && !texture.hasPremultipliedAlpha()) {
                blendFunc.src = GL10.GL_SRC_ALPHA;
                blendFunc.dst = GL10.GL_ONE_MINUS_SRC_ALPHA;
            } else {
                blendFunc.src = ccConfig.CC_BLEND_SRC;
                blendFunc.dst = ccConfig.CC_BLEND_DST;
            }
        }
    }

    /**
     * @struct tCCParticle
     * Structure that contains the values of each particle
     */
    static class CCParticle {
        CGPoint pos = new CGPoint();
        CGPoint startPos = new CGPoint();
        ccColor4F color = new ccColor4F();
        ccColor4F deltaColor = new ccColor4F();
        float size;
        float deltaSize;
        float rotation;
        float deltaRotation;
        float timeToLive;
        ParticleModeA modeA;
        ParticleModeB modeB;

        static class ParticleModeA {
            CGPoint dir = new CGPoint();
            float radialAccel;
            float tangentialAccel;
        }

        // Mode B: radius mode
        static class ParticleModeB {
            float angle;
            float degreesPerSecond;
            float radius;
            float deltaRadius;
        }
    }

    // Mode A:Gravity + Tangential Accel + Radial Accel
    class ModeA {
        // gravity of the particles
        CGPoint gravity = CGPoint.zero();

        // The speed the particles will have.
        float speed;
        // The speed variance
        float speedVar;

        // Tangential acceleration
        float tangentialAccel;
        // Tangential acceleration variance
        float tangentialAccelVar;

        // Radial acceleration
        float radialAccel;
        // Radial acceleration variance
        float radialAccelVar;
    }

    // Mode B: circular movement (gravity, radial accel and tangential accel don't are not used in this mode)
    class ModeB {
        // The starting radius of the particles
        float startRadius;
        // The starting radius variance of the particles
        float startRadiusVar;
        // The ending radius of the particles
        float endRadius;
        // The ending radius variance of the particles
        float endRadiusVar;
        // Number of degress to rotate a particle around the source pos per second
        float rotatePerSecond;
        // Variance in degrees for rotatePerSecond
        float rotatePerSecondVar;
    }

}


